﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace CWM.Skinn
{
    public static partial class SkinnEx
    {
        internal class MeshVertexData
        {
            [NonSerialized] public string name;
            [NonSerialized] public List<Vector3> vertices, normals;
            [NonSerialized] public List<Vector2> uv, uv2, uv3, uv4;

#if UNITY_2018_1_OR_NEWER
            [NonSerialized] public List<Vector2> uv5, uv6, uv7;// uv8;
#endif
            [NonSerialized] public List<Vector4> tangents;
            [NonSerialized] public List<Color32> colors32;
            [NonSerialized] public List<BoneWeight> boneWeights;

            public int VertexCount { get { return vertices == null ? 0 : vertices.Count; } }

            public MeshVertexData() { vertices = new List<Vector3>(); name = ""; }

            public MeshVertexData(Mesh mesh)
            {
                name = EnforceString(mesh.name);
                vertices = GetValues(mesh.vertices); normals = GetValues(mesh.normals);
                uv = GetValues(mesh.uv); uv2 = GetValues(mesh.uv2); uv3 = GetValues(mesh.uv3); uv4 = GetValues(mesh.uv4);

#if UNITY_2018_1_OR_NEWER
                uv5 = GetValues(mesh.uv5); uv6 = GetValues(mesh.uv6); uv7 = GetValues(mesh.uv7);// uv8 = GetValues(mesh.uv8);
#endif
                tangents = GetValues(mesh.tangents);
                colors32 = GetValues(mesh.colors32);
                boneWeights = GetValues(mesh.boneWeights);
            }

            private List<T> GetValues<T>(T[] source)
            {
                if (source == null || source.Length == 0) return null;
                return new List<T>(source);
            }

            private void AddVertex<T>(ref List<T> target, List<T> source, int index)
            {
                if (source == null) return;
                if (target == null) target = new List<T>();
                target.Add(source[index]);
            }

            public int AddVertexData(MeshVertexData source, int index)
            {
                int i = vertices.Count;
                AddVertex(ref vertices, source.vertices, index); AddVertex(ref normals, source.normals, index);
                AddVertex(ref uv, source.uv, index); AddVertex(ref uv2, source.uv2, index);
                AddVertex(ref uv3, source.uv3, index); AddVertex(ref uv4, source.uv4, index);

#if UNITY_2018_2_OR_NEWER
                AddVertex(ref uv5, source.uv5, index); AddVertex(ref uv6, source.uv6, index);
                AddVertex(ref uv7, source.uv7, index);// AddVertex(ref uv8, source.uv8, index);
#endif

                AddVertex(ref tangents, source.tangents, index);
                AddVertex(ref colors32, source.colors32, index);
                AddVertex(ref boneWeights, source.boneWeights, index);
                return i;
            }

            public Mesh GetMesh(int[]triangles, Matrix4x4[] bindposes = null)
            {
                Mesh mesh = new Mesh() { name = name };
                mesh.SetVertices(vertices); if(normals != null) mesh.SetNormals(normals);
                if (uv != null) mesh.SetUVs(0, uv); if (uv2 != null) mesh.SetUVs(1, uv2);
                if (uv3 != null) mesh.SetUVs(2, uv3); if (uv4 != null) mesh.SetUVs(3, uv4);
#if UNITY_2018_2_OR_NEWER
                if (uv5 != null) mesh.SetUVs(5, uv5); if (uv6 != null) mesh.SetUVs(6, uv6);
                if (uv7 != null) mesh.SetUVs(7, uv7);// if (uv8 != null) mesh.SetUVs(8, uv8);
#endif
                if (tangents != null) mesh.SetTangents(tangents);
                if (colors32 != null) mesh.SetColors(colors32);
                if (boneWeights != null) mesh.boneWeights = boneWeights.ToArray();
                mesh.triangles = triangles;
                mesh.bindposes = bindposes;
                return mesh;
            }

            public Mesh GetMesh(Dictionary<int, int[]> triangleIndices, Matrix4x4[] bindposes = null)
            {
                if (IsNullOrEmpty(vertices)) return null;
                var mesh = new Mesh() { name = name};
                int vertexCount = vertices.Count;
                mesh.SetVertices(vertices);
                mesh.subMeshCount = triangleIndices.Count;
                foreach (var item in triangleIndices) mesh.SetTriangles(item.Value, item.Key);
                if (!IsNullOrEmpty(uv, vertexCount)) mesh.SetUVs(0, uv);
                if (!IsNullOrEmpty(uv2, vertexCount)) mesh.SetUVs(1, uv2);
                if (!IsNullOrEmpty(uv3, vertexCount)) mesh.SetUVs(2, uv3);
                if (!IsNullOrEmpty(uv4, vertexCount)) mesh.SetUVs(3, uv4);

#if UNITY_2018_2_OR_NEWER
                if (!IsNullOrEmpty(uv5, vertexCount)) mesh.SetUVs(4, uv5);
                if (!IsNullOrEmpty(uv6, vertexCount)) mesh.SetUVs(5, uv6);
                if (!IsNullOrEmpty(uv7, vertexCount)) mesh.SetUVs(6, uv7);
                //if (!IsNullOrEmpty(uv8, vertexCount)) mesh.SetUVs(7, uv8);
#endif
                if (!IsNullOrEmpty(normals, vertexCount)) mesh.SetNormals(normals);
                if (!IsNullOrEmpty(tangents, vertexCount)) mesh.SetTangents(tangents);
                if (!IsNullOrEmpty(colors32, vertexCount)) mesh.colors32 = colors32.ToArray();
                if (boneWeights != null) mesh.boneWeights = boneWeights.ToArray();
                mesh.bindposes = bindposes;
                return mesh;
            }

            public int GetVertexKey(int index, string prefix = "", string format = "0.0")
            {
                var source = this;
                var key = prefix;

                var vertex = source.vertices[index] * (1f).ToUnitFromStandard(ConversionUnits.Inch);
                key += "v_";
                key += vertex.x.ToString(format); key += vertex.y.ToString(format); key += vertex.z.ToString(format);

                var normal = !IsNullOrEmpty(source.normals) ? source.normals[index] : Vector3.zero;
                key += "n_";
                key += normal.x.ToString(format); key += normal.y.ToString(format); key += normal.z.ToString(format);
               
                if (!IsNullOrEmpty(source.tangents))
                {
                    var tangent = source.tangents[index];
                    key += "t_";
                    key += tangent.x.ToString(format); key += tangent.y.ToString(format);
                    key += tangent.z.ToString(format); key += tangent.w.ToString(format);
                }

                if (!IsNullOrEmpty(source.colors32))
                {
                    var color = (Vector4)(Color)source.colors32[index];
                    key += "c_";
                    key += color.x.ToString(format); key += color.y.ToString(format);
                    key += color.z.ToString(format); key += color.w.ToString(format);
                }

                if (!IsNullOrEmpty(source.boneWeights))
                {
                    var boneWeight = (BoneWeightData)source.boneWeights[index];
                    key += "w_";
                    for (int i = 0; i < boneWeight.weights.Length; i++)
                    {
                        key += boneWeight.weights[i].x.ToString(format); key += boneWeight.weights[i].y.ToString(format);
                    }
                }

                UVFormat(ref key, source.uv, index, format);
                UVFormat(ref key, source.uv2, index, format);
                UVFormat(ref key, source.uv3, index, format);
                UVFormat(ref key, source.uv4, index, format);

#if UNITY_2018_2_OR_NEWER
                UVFormat(ref key, source.uv5, index, format);
                UVFormat(ref key, source.uv6, index, format);
                UVFormat(ref key, source.uv7, index, format);
                //UVFormat(ref key, source.uv8, index, format);
#endif

                return Animator.StringToHash(key);
            }

            private static void UVFormat(ref string hashKey, List<Vector2> uvs, int index, string format = "0.00001")
            {
                if (IsNullOrEmpty(uvs)) return;
                Vector2 uv = uvs[index];
                uv.x = Mathf.Abs(Mathf.Repeat(uv.x, 1f) - 0.5f);
                uv.y = Mathf.Abs(Mathf.Repeat(uv.y, 1f) - 0.5f);
                hashKey += "uv_"; hashKey += uv.x.ToString(format); hashKey += uv.y.ToString(format);
            }

            public static implicit operator bool(MeshVertexData value) { return value != null; }
        }

        internal class MeshVertexInfo
        {
            public int vertexCount;
            public int triangleCount;
            public string fullName;

            public bool hasNormals, hasTangents, hasColors, hasUV, hasUV2, hasUV3, hasUV4, hasUV5, hasUV6, hasUV7, hasUV8,
               hasBoneWeights, hasBindposes, hasBlendshapes, hasTriangles;

            public MeshVertexInfo() { Reset(); }

            public MeshVertexInfo(Mesh mesh) { Reset(); Add(mesh); }

            public void Add(Mesh mesh)
            {
                if (!mesh) return;
                if (IsNullOrEmpty(mesh.vertices)) return;
                int vertexCount = mesh.vertices.Length;
                if (!IsNullOrEmpty(mesh.normals, vertexCount)) hasNormals = true;
                if (!IsNullOrEmpty(mesh.tangents, vertexCount)) hasTangents = true;
                if (!IsNullOrEmpty(mesh.colors32, vertexCount)) hasColors = true;
                if (!IsNullOrEmpty(mesh.uv, vertexCount)) hasUV = true;
                if (!IsNullOrEmpty(mesh.uv2, vertexCount)) hasUV2 = true;
                if (!IsNullOrEmpty(mesh.uv3, vertexCount)) hasUV3 = true;
                if (!IsNullOrEmpty(mesh.uv4, vertexCount)) hasUV4 = true;


#if UNITY_2018_2_OR_NEWER
                if (!IsNullOrEmpty(mesh.uv5, vertexCount)) hasUV5 = true;
                if (!IsNullOrEmpty(mesh.uv6, vertexCount)) hasUV6 = true;
                if (!IsNullOrEmpty(mesh.uv7, vertexCount)) hasUV7 = true;
                //if (!IsNullOrEmpty(mesh.uv8, vertexCount)) hasUV8 = true;
#endif


                if (!IsNullOrEmpty(mesh.boneWeights, vertexCount)) hasBoneWeights = true;
                if (!IsNullOrEmpty(mesh.bindposes) && mesh.bindposes.Length > 0) hasBindposes = true;
                if (mesh.blendShapeCount > 0) hasBlendshapes = true;
                this.vertexCount += vertexCount;
                if (!IsNullOrEmpty(mesh.triangles)) { hasTriangles = true; triangleCount += mesh.triangles.Length; };

                fullName += string.Format(".{0}", EnforceString(mesh.name));
            }

            public void Add(Renderer source) { if (!source) return; else Add(source.GetSharedMesh()); }

            public void Reset()
            {
                vertexCount = 0;
                triangleCount = 0;
                fullName = "";
                hasNormals = false; hasTangents = false; hasColors = false; hasUV = false; hasUV2 = false; hasUV3 = false;
                hasUV4 = false; hasUV5 = false; hasUV6 = false; hasUV7 = false; hasUV8 = false;
                hasBoneWeights = false; hasBindposes = false; hasBlendshapes = false; hasTriangles = false;
            }

            public static implicit operator bool(MeshVertexInfo value) { return value != null; }
        }

        internal class MeshVertexDataCombined
        {
            private MeshVertexInfo vertexInfo;

            [NonSerialized] public List<Vector3> vertices, normals;
            [NonSerialized] public List<Vector2> uv, uv2, uv3, uv4;

#if UNITY_2018_2_OR_NEWER
            [NonSerialized] public List<Vector2> uv5, uv6, uv7;//, uv8;
#endif

            [NonSerialized] public List<Vector4> tangents;
            [NonSerialized] public List<Color32> colors32;

            public MeshVertexDataCombined(MeshVertexInfo vertexInfo)
            {
                this.vertexInfo = vertexInfo;
                vertices = new List<Vector3>(vertexInfo.vertexCount);
                if (vertexInfo.hasNormals) normals = new List<Vector3>(vertexInfo.vertexCount);
                if (vertexInfo.hasUV) uv = new List<Vector2>(vertexInfo.vertexCount);
                if (vertexInfo.hasUV2) uv2 = new List<Vector2>(vertexInfo.vertexCount);
                if (vertexInfo.hasUV3) uv3 = new List<Vector2>(vertexInfo.vertexCount);
                if (vertexInfo.hasUV4) uv4 = new List<Vector2>(vertexInfo.vertexCount);


#if UNITY_2018_2_OR_NEWER
                if (vertexInfo.hasUV5) uv5 = new List<Vector2>(vertexInfo.vertexCount);
                if (vertexInfo.hasUV6) uv6 = new List<Vector2>(vertexInfo.vertexCount);
                if (vertexInfo.hasUV7) uv7 = new List<Vector2>(vertexInfo.vertexCount);
                //if (vertexInfo.hasUV8) uv8 = new List<Vector2>(vertexInfo.vertexCount);
#endif

                if (vertexInfo.hasTangents) tangents = new List<Vector4>(vertexInfo.vertexCount);
                if (vertexInfo.hasColors) colors32 = new List<Color32>(vertexInfo.vertexCount);
            }

            private void AddVertex(ref List<Vector3> target, List<Vector3> source, int index, Vector3 defualt)
            {
                if (IsNullOrEmpty(source)) target.Add(defualt); else target.Add(source[index]);
            }

            private void AddVertex(ref List<Vector2> target, List<Vector2> source, int index, Vector2 defualt)
            {
                if (IsNullOrEmpty(source)) target.Add(defualt); else target.Add(source[index]);
            }

            private void AddVertex(ref List<Vector4> target, List<Vector4> source, int index, Vector4 defualt)
            {
                if (IsNullOrEmpty(source)) target.Add(defualt); else target.Add(source[index]);
            }

            private void AddVertex(ref List<Color32> target, List<Color32> source, int index, Color defualt)
            {
                if (IsNullOrEmpty(source)) target.Add(defualt); else target.Add(source[index]);
            }

            private void AddVertex(ref List<Color> target, List<Color> source, int index, Color defualt)
            {
                if (IsNullOrEmpty(source)) target.Add(defualt); else target.Add(source[index]);
            }

            public int AddVertexData(MeshVertexData source, int index)
            {
                int i = vertices.Count;
                AddVertex(ref vertices, source.vertices, index, Vector3.zero);
                if (vertexInfo.hasUV) AddVertex(ref uv, source.uv, index, Vector2.zero);
                if (vertexInfo.hasUV2) AddVertex(ref uv2, source.uv2, index, Vector2.zero);
                if (vertexInfo.hasUV3) AddVertex(ref uv3, source.uv3, index, Vector2.zero);
                if (vertexInfo.hasUV4) AddVertex(ref uv4, source.uv4, index, Vector2.zero);

#if UNITY_2018_2_OR_NEWER
                if (vertexInfo.hasUV5) AddVertex(ref uv5, source.uv5, index, Vector2.zero);
                if (vertexInfo.hasUV6) AddVertex(ref uv6, source.uv6, index, Vector2.zero);
                if (vertexInfo.hasUV7) AddVertex(ref uv7, source.uv7, index, Vector2.zero);
                //if (vertexInfo.hasUV8) AddVertex(ref uv8, source.uv8, index, Vector2.zero);
#endif

                if (vertexInfo.hasNormals) AddVertex(ref normals, source.normals, index, Vector3.one.normalized);
                if (vertexInfo.hasTangents) AddVertex(ref tangents, source.tangents, index, Vector4.one.normalized);
                if (vertexInfo.hasColors) AddVertex(ref colors32, source.colors32, index, Color.white);
                return i;
            }


            public Mesh GetMesh(Dictionary<int, int[]> triangleIndices)
            {
                if (IsNullOrEmpty(vertices)) return null;
                var mesh = new Mesh() { name = "Combined" + vertexInfo.fullName.ClampEllipsis(20) };
                
                var vertexCount = vertices.Count;

                if(vertexCount >= 65535) //https://docs.unity3d.com/ScriptReference/Mesh-indexFormat.html
                    mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
                else
                    mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt16;

                mesh.SetVertices(vertices);
                mesh.subMeshCount = triangleIndices.Count;
                foreach (var item in triangleIndices) mesh.SetTriangles(item.Value, item.Key);
                if (!IsNullOrEmpty(uv, vertexCount)) mesh.SetUVs(0, uv);
                if (!IsNullOrEmpty(uv2, vertexCount)) mesh.SetUVs(1, uv2);
                if (!IsNullOrEmpty(uv3, vertexCount)) mesh.SetUVs(2, uv3);
                if (!IsNullOrEmpty(uv4, vertexCount)) mesh.SetUVs(3, uv4);

#if UNITY_2018_2_OR_NEWER
                if (!IsNullOrEmpty(uv5, vertexCount)) mesh.SetUVs(4, uv5);
                if (!IsNullOrEmpty(uv6, vertexCount)) mesh.SetUVs(5, uv6);
                if (!IsNullOrEmpty(uv7, vertexCount)) mesh.SetUVs(6, uv7);
                //if (!IsNullOrEmpty(uv8, vertexCount)) mesh.SetUVs(7, uv8);
#endif

                if (!IsNullOrEmpty(normals, vertexCount)) mesh.SetNormals(normals);
                if (!IsNullOrEmpty(tangents, vertexCount)) mesh.SetTangents(tangents);
                if (!IsNullOrEmpty(colors32)) mesh.colors32 = colors32.ToArray();
                return mesh;
            }

            public static implicit operator bool(MeshVertexDataCombined value) { return value != null; }
        }

    }
}